package assembly;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.Scanner;
import simulation.ILoadable;
import simulation.SpringiesConstants;
import util.Location;
import util.Pixmap;
import util.Sprite;
import util.Vector;


/**
 * Spring that connects two masses
 * 
 * @author Robert C. Duvall
 */
public class Spring extends Sprite {

    /**
     * image to be used for the spring
     */
    public static final Pixmap DEFAULT_IMAGE = new Pixmap("spring.gif");
    /**
     * the 'width' of the spring image. Used as a dimension when the program uses DEFAULT_IMAGE
     */
    public static final int IMAGE_HEIGHT = 20;
    /**
     * the default rest length of the spring
     */
    public static final double DEFAULT_LENGTH = 100.0;

    /**
     * the default k constant for the 'springiness' of the spring
     */
    public static final double DEFAULT_K = .2;
    private static final String KEYWORD = SpringiesConstants.SPRING_KEYWORD;

    // private static Spring sample;

    private Mass myStart;
    private Mass myEnd;
    private double myLength;
    private double myK;

    /**
     * Constructor
     * 
     * @param start - one of the end masses
     * @param end - the other end mass
     * @param length - rest length of spring
     * @param kVal - k constant of spring -> "how bouncy it is"
     */
    public Spring (Mass start, Mass end, double length, double kVal) {
        super(DEFAULT_IMAGE, getCenter(start, end), getSize(start, end));
        myStart = start;
        myEnd = end;
        myLength = length;
        myK = kVal;
    }

    /**
     * creates spring based on default values
     */
    public Spring () {
        this(new Mass(), new Mass(), DEFAULT_LENGTH, DEFAULT_K);
    }

    /**
     * creates spring based on default values
     * 
     * @param p to act as one end of the spring
     * @param m - mass for spring to connect to
     */
    public Spring (Point2D p, Mass m) {
        this(new Mass(p, 0), m, m.distance(p), DEFAULT_K);
    }

    @Override
    public void paint (Graphics2D pen) {
        pen.setColor(getColor(myStart.distance(myEnd) - myLength));
        pen.drawLine((int) myStart.getX(), (int) myStart.getY(), (int) myEnd.getX(),
                     (int) myEnd.getY());
    }

    /**
     * changes color of the spring based on current forces
     * red = extension force
     * blue = compression force
     * 
     * @param diff - difference between the actual length of the spring and the rest length of the
     *        spring
     * @return - the color of the spring
     */
    protected Color getColor (double diff) {
        if (Vector.fuzzyEquals(diff, 0)) {
            return Color.BLACK;
        }
        else if (diff < 0.0) {
            return Color.BLUE;
        }
        else {
            return Color.RED;
        }
    }

    /**
     * computes the center of the spring
     * 
     * @param start - one mass at end of spring
     * @param end - other mass at end of spring
     * @return - the point halfway between start and end
     */
    protected static Location getCenter (Mass start, Mass end) {
        return new Location((start.getX() + end.getX()) / 2, (start.getY() + end.getY()) / 2);
    }

    /**
     * computes size of spring
     * 
     * @param start - one mass at end of spring
     * @param end - other mass at end of spring
     * @return the dimension that the spring currently occupies
     */
    protected static Dimension getSize (Mass start, Mass end) {
        return new Dimension((int) start.distance(end), IMAGE_HEIGHT);
    }

    /**
     * gets the start mass of this spring
     * 
     * @return one of the end masses (start)
     */
    public Mass getStart () {
        return myStart;
    }

    /**
     * gets the end mass of this spring
     * 
     * @return one of the end masses (end)
     */
    public Mass getEnd () {
        return myEnd;
    }

    /**
     * returns the rest length of the spring
     * 
     * @return - the rest length
     */
    public double getLength () {
        return myLength;
    }

    /**
     * checks to see if set length is positive, if it is reset the rest length of this spring
     * 
     * @param length is the new rest length of the spring
     */
    public void setLength (double length) {
        if (length > 0) {
            this.myLength = length;
        }
    }

    /**
     * gives the k constant for this spring
     * 
     * @return the k constant
     *         >>>>>>> master
     */
    public double getK () {
        return myK;
    }

    @Override
    public void update (double elapsedTime, Dimension bounds) {
        double dx = myStart.getX() - myEnd.getX();
        double dy = myStart.getY() - myEnd.getY();
        // apply hooke's law to each attached mass
        Vector force =
                new Vector(Vector.angleBetween(dx, dy), myK *
                                                        (myLength - Vector.distanceBetween(dx, dy)));
        myStart.applyForce(force);
        force.negate();
        myEnd.applyForce(force);
        // update sprite values based on attached masses
        setCenter(getCenter(myStart, myEnd));
        setSize(getSize(myStart, myEnd));
        setVelocity(Vector.angleBetween(dx, dy), 0);
    }

    @Override
    public ILoadable<Sprite> load (Scanner line, Assembly a) {
        // Very curious fellow
        Mass m1 = a.getMasses().get(line.nextInt());
        Mass m2 = a.getMasses().get(line.nextInt());
        double restLength = line.nextDouble();
        double ks = line.nextDouble();
        return new Spring(m1, m2, restLength, ks);
    }

    @Override
    public String getKeyword () {
        return KEYWORD;
    }
}
